package il.ac.bgu.cs.heuristics.graph;

import il.ac.bgu.cs.heuristics.graph.pddl.auxiliary.ContradictionResolver;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.ITypedConstant;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.LogicalOperator;
import il.ac.bgu.cs.heuristics.graph.pddl.parser.types.Predicate;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;
import il.ac.bgu.cs.heuristics.utils.Validation;

import java.util.Collections;
import java.util.List;

/**
 * @author Ronen Pinko
 * 
 */
public class Substitution implements ISubstitution, Cloneable { // NOPMD

	private final List<ITypedConstant>	substitutedConstants;
	private final Predicate				predicate;
	private final ContradictionResolver	resolver;
	private final boolean				result;
	private final String				stringRepresentation;

	private Substitution(Builder builder) {
		this.substitutedConstants = builder.substitutedConstants;
		this.predicate = builder.predicate;
		this.resolver = builder.resolver;
		this.result = predicate.getOperator() != LogicalOperator.NOT;
		this.stringRepresentation = createStringRepresentation();
	}

	@Override
	public boolean isContradicting(ISubstitution other) {
		return isNegateSubstitution(other) ? true : resolver.resolve(this,
				other);
	}

	// return T iff the Substitution is same except the logical operator is
	// negate
	@Override
	public boolean isNegateSubstitution(ISubstitution other) {
		boolean operatorContradiction = false;
		final Predicate otherPredicate = other.getPredicate();
		if (predicate.getName().equals(otherPredicate.getName())
				&& substitutedConstants.equals(other.getSubstitutedConstants())) {
			operatorContradiction = predicate.getOperator().contradicts(
					otherPredicate.getOperator());
		}
		return operatorContradiction;
	}

	@Override
	public boolean result() {
		return result;
	}

	@Override
	public Predicate getPredicate() {
		return predicate;
	}

	@Override
	public List<ITypedConstant> getSubstitutedConstants() {
		return Collections.unmodifiableList(substitutedConstants);
	}

	/**
	 * NOTE: this representation is subject to change and should never be used
	 * to perform serialization of this object (RP)
	 */
	@Override
	public String toString() {
		return stringRepresentation;
	}

	private String createStringRepresentation() {
		final StringBuilder sb = new StringBuilder();
		sb.append("sub:");
		sb.append('[');
		sb.append(String.format(predicate.getStringTemplate(),
				substitutedConstants.toArray()));
		sb.append('=');
		sb.append(result);
		sb.append(']');
		return sb.toString();
	}

	@Override
	public Substitution clone() throws CloneNotSupportedException {
		return (Substitution) super.clone();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((predicate == null) ? 0 : predicate.hashCode());
		result = prime * result + (this.result ? 1231 : 1237);
		result = prime
				* result
				+ ((substitutedConstants == null) ? 0 : substitutedConstants
						.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) { // NOPMD (generated code)
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final Substitution other = (Substitution) obj; // NOPMD generated code
		if (predicate == null) {
			if (other.predicate != null) {
				return false;
			}
		} else if (!predicate.equals(other.predicate)) {
			return false;
		}
		if (result != other.result) {
			return false;
		}
		if (substitutedConstants == null) {
			if (other.substitutedConstants != null) {
				return false;
			}
		} else if (!substitutedConstants.equals(other.substitutedConstants)) {
			return false;
		}
		return true;
	}

	public final static class Builder {

		private final Predicate				predicate;

		private final List<ITypedConstant>	substitutedConstants	= CollectionUtils
																			.newArrayList();

		private final ContradictionResolver	resolver;

		public Builder(Predicate predicate, ContradictionResolver resolver) {
			this.resolver = resolver;
			this.predicate = Validation.ensureArgumentNeverNull(predicate);
		}

		public Builder(Substitution other) {
			// predicate is immutable, no need to clone
			this.predicate = other.getPredicate();
			// variable is immutable, no need to clone
			this.substitutedConstants.addAll(other.getSubstitutedConstants());
			this.resolver = other.resolver;
		}

		public Builder addConstant(ITypedConstant typedConstant) {
			substitutedConstants.add(Validation
					.ensureArgumentNeverNull(typedConstant));
			return this;
		}

		public Substitution build() {
			Validation.ensureArgumentSizeEquals(substitutedConstants, predicate
					.arity());
			return new Substitution(this); // NOPMD builder pattern
		}

	}

}
